#include <iomanip>
#include "sc_server.h"
#include "src/filesystem/be_filesystem.h"

ScServer::ScServer() : m_startRace(false), m_idcounter(0)
{
	settings = Settings::Instance();
	
	// BULLET SETUP
	m_dynamicsWorld = m_physicsSystem.createWorld();

	const char * mapName = settings->getCVarS("map").c_str();
	m_map = Map::LoadMap( mapName, m_dynamicsWorld );
	m_entities.push_back(m_map);	//
}

void ScServer::process( float timeDelta )
{
	// PARSE INCOMING MESSAGES
		parseIncomingMessages();

	// PROCESS PLAYERS
		for ( unsigned int i=0; i < m_players.size(); i++ )
		{
			boost::shared_ptr<Player> p = m_players[i];
			if ( p->isJoined() )
			{
				// apply forces
				p->getCar()->applyForces(timeDelta, p->getInputtype() );
			
				// if < -1000 unjoin this player
				if ( static_cast<BodypartRigid*>(p->getCar()->body.m_bodyparts[0].get())->m_motionState->m_graphicsWorldTrans.getOrigin().getY() < -1000.0f )
				{
					p->pushIncomingmessage("uj 1;");
				}
			}
		}

	// SEND OUTGOING MESSAGES
		for ( unsigned int k=0; k < m_players.size(); k++ )
		{
			boost::shared_ptr<Player> p = m_players[k];
			if ( p->isApproved() )
			{
				stringstream carpos;
				for ( unsigned int i=0; i < m_players.size(); i++ )
				{
					boost::shared_ptr<Player> otherplayer = m_players[i];
					if ( otherplayer->isJoined() )
					{
						// if this is first joined player, add "cp" meaning list of player positions
						if ( carpos.str().size() == 0 )
							carpos << "cp";
						
						// add player id
						carpos << " " << otherplayer->getId();
						
						if ( i == k )
						{
							carpos << " " << otherplayer->getCar()->m_vehicle->getCurrentSpeedKmHour();
						}

						btScalar x(0), y(0), z(0);

						btVector3 pos = static_cast<BodypartRigid*>(otherplayer->getCar()->body.m_bodyparts[0].get())->m_motionState->m_graphicsWorldTrans.getOrigin();
						static_cast<BodypartRigid*>(otherplayer->getCar()->body.m_bodyparts[0].get())->m_motionState->m_graphicsWorldTrans.getBasis().getEulerYPR(x, y, z);

						carpos << " " << pos.getX() << " " << pos.getY() << " " << pos.getZ();
						carpos << " " << x << " " << y << " " << z;
						carpos << " " << otherplayer->getCar()->m_vehicle->getSteeringValue(0);
					}
				}

				if ( carpos.str().size() > 0 )
				{
					carpos << ";";
					p->pushOutgoingmessage( carpos.str() );
				}

				// send force feedback info if joystick
				if ( p->isJoined() ) //  && p->getInputtype() == JOYSTICK
				{
					stringstream ff;
					ff << "ff " << p->getCar()->m_vehicle->forwardWheelSumSide << ";";
					p->pushOutgoingmessage( ff.str() );
				}
			}
		}

	// PHYSICS
		m_dynamicsWorld->stepSimulation( timeDelta, 100, 0.01 );

	// GAME LOGIC
		processrace();
}

void ScServer::parseIncomingMessages()
{
	for ( unsigned int playerid=0; playerid < m_players.size(); playerid++ )
	{
		boost::shared_ptr<Player> p = m_players[playerid];

		for ( unsigned int i=0; i < p->m_messages_in.size(); i++ )
		{
			string msg = parseH.returnUntillStrip( ";", p->m_messages_in[i] );

			while ( msg.size() > 0 )
			{
// 				cerr << msg << " - " << endl;
				string ntype = parseH.returnUntillStrip( " ", msg );

				// car motion, analog
				if ( ntype == "ca" )
				{
					if ( p->isJoined() ) //  && p->getInputtype() == JOYSTICK
					{
						string axisS = parseH.returnUntillStrip( " ", msg );
						string valueS = msg;
						float value = atof( valueS.c_str() );

						// 0 = steering, 1 = throttle, 2 = braking
						if ( axisS == "0" )
						{
							if ( value >= -1.0f && value <= 1.0f )
								p->getCar()->gVehicleSteering = -value;
						}
						else if ( axisS == "1" )
						{
							// throttle
							if ( value >= -1.0f && value <= 1.0f )
								p->getCar()->gEngineForce = value * p->getCar()->maxEngineForce;
						}
						else if ( axisS == "2" )
						{
							// braking
							if ( value >= 0.0f && value <= 1.0f )
								p->getCar()->gBreakingForce = value * p->getCar()->defaultBreakingForce;
						}
					}
				}

				// version check & connect
				else if ( ntype == "v" )
				{
					if ( msg == settings->getCVarS("version") && !p->isApproved() )
					{
						p->approve();
						cerr << "player " << p->getId() << " version confirmed" << endl;
						stringstream newlog; newlog << "player " << p->getId() << " connected";
						newLogMessage(newlog.str());

						// send playerlist
							stringstream playerlist;
							playerlist << "pl";	 	// playerlist
							playerlist << " " << p->getId();	 // this player id

							for ( unsigned int i=0; i < m_players.size(); i++ )
							{
								boost::shared_ptr<Player> otherplayer = m_players[i];
								if ( otherplayer->isApproved() )
								{
									stringstream m_playerstr;
									
									// id
									m_playerstr << " " << otherplayer->getId();

									// joined
									if ( otherplayer->isJoined() )
										m_playerstr << " 1";
									else
										m_playerstr << " 0";

									playerlist << m_playerstr.str();
								}
							}
							playerlist << ";";

						// map change
						stringstream mapchange;
						mapchange << "mc "  << settings->getCVarS("map") << ";";
	// 					cerr << mapchange.str() << endl;

						p->pushOutgoingmessage( mapchange.str() );
						p->pushOutgoingmessage( playerlist.str() );

						// message new player to all clients
						stringstream newplayer;
						newplayer << "np " << p->getId() << ";";
						newMessageAllClients( newplayer.str() );
					}
				}

				// start race
				else if ( ntype == "sr" )
				{
					// confirmation
					if ( msg == "1" && p->isJoined() )
						m_startRace = true;
				}

				// player join
				else if ( ntype == "jo" )
				{
					cerr << "player " << endl;
					// confirmation
					if ( msg == "1" )
					{
						if ( !p->isJoined() && p->isApproved() )
						{
							cerr << "player " << p->getId() << " joins" << endl;
								cerr << "adding car" << endl;
								p->join( boost::shared_ptr<Car>(new Car( p->getId(), m_map->startgrid, m_dynamicsWorld) ) );
							
							// message event to all clients
							stringstream newjoin;
							newjoin << "jo " << p->getId() << ";";
	// 						cerr << "sending " << newjoin.str() << endl;
							newMessageAllClients( newjoin.str() );

							stringstream newjoinlog; newjoinlog << "player " << p->getId() << " joined";
	// 						Logbuffer::Instance()->add(newjoinlog);
							newLogMessage(newjoinlog.str());
						}
					}
				}

				// player unjoin
				else if ( ntype == "uj" )
				{
					// confirmation
					if ( msg == "1" )
					{
						if ( p->isJoined() )
						{
							cerr << "player " << p->getId() << " unjoins" << endl;
								p->unjoin();
							
							// message event to all clients
							stringstream newunjoin;
							newunjoin << "uj "  << p->getId() << ";";
	// 						cerr << "sending " << newunjoin.str() << endl;
							newMessageAllClients( newunjoin.str() );

							stringstream newjoinlog; newjoinlog << "player " << p->getId() << " unjoined";
	// 						Logbuffer::Instance()->add(newjoinlog);
							newLogMessage(newjoinlog.str());
						}
					}
				}

				// joystick toggle
				else if ( ntype == "js" )
				{
					if ( msg == "0" )
							p->setInputtype(KEYBOARD);
					else if ( msg == "1" )
							p->setInputtype(JOYSTICK);
				}

				// car settings
				else if ( ntype == "cs1" )
				{
					if ( p->isJoined() )
					{
						int carsetting(0);
						string str = msg; if ( !str.empty() ) carsetting = atoi( str.c_str() );

						for ( int i=0; i < p->getCar()->m_vehicle->getNumWheels(); i++ )
							p->getCar()->m_vehicle->getWheelInfo(i).m_suspensionStiffness = 0.01f * carsetting;
					}
				}
				else if ( ntype == "cs2" )
				{
					if ( p->isJoined() )
					{
						int carsetting(0);
						string str = msg; if ( !str.empty() ) carsetting = atoi( str.c_str() );

						for ( int i=0; i < p->getCar()->m_vehicle->getNumWheels(); i++ )
							p->getCar()->m_vehicle->getWheelInfo(i).m_wheelsDampingRelaxation = 0.002f * carsetting;
					}
				}
				else if ( ntype == "cs3" )
				{
					if ( p->isJoined() )
					{
						int carsetting(0);
						string str = msg; if ( !str.empty() ) carsetting = atoi( str.c_str() );

						for ( int i=0; i < p->getCar()->m_vehicle->getNumWheels(); i++ )
							p->getCar()->m_vehicle->getWheelInfo(i).m_wheelsDampingCompression = 0.01f * carsetting;
					}
				}
				else if ( ntype == "cs4" )
				{
					if ( p->isJoined() )
					{
						int carsetting(0);
						string str = msg; if ( !str.empty() ) carsetting = atoi( str.c_str() );

						for ( int i=0; i < p->getCar()->m_vehicle->getNumWheels(); i++ )
							p->getCar()->m_vehicle->getWheelInfo(i).m_rollInfluence = 0.0001f * carsetting;
					}
				}
				else if ( ntype == "cs5" )
				{
					if ( p->isJoined() )
					{
						int carsetting(0);
						string str = msg; if ( !str.empty() ) carsetting = atoi( str.c_str() );

	//					p->getCar()->steeringIncrement = 0.00001f * carsetting;
						p->getCar()->steeringIncrement = 0.002*carsetting;
					}
				}
				else if ( ntype == "cs6" )
				{
					if ( p->isJoined() )
					{
						int carsetting(0);
						string str = msg; if ( !str.empty() ) carsetting = atoi( str.c_str() );

						p->getCar()->steeringClamp = 0.001f * carsetting;
					}
				}
				else if ( ntype == "cs7" )
				{
					if ( p->isJoined() )
					{
						int carsetting(0);
						string str = msg; if ( !str.empty() ) carsetting = atoi( str.c_str() );

						p->getCar()->steeringExponentiality = carsetting;
					}
				}
				else if ( ntype == "cs8" )
				{
					if ( p->isJoined() )
					{
						int carsetting(0);
						string str = msg; if ( !str.empty() ) carsetting = atoi( str.c_str() );

						for ( int i=0; i < p->getCar()->m_vehicle->getNumWheels(); i++ )
							p->getCar()->m_vehicle->getWheelInfo(i).m_suspensionRestLength1 = 0.001f * carsetting;
					}
				}

				else
				{
					cerr << "unknown message: " << ntype << endl;
				}

				msg = parseH.returnUntillStrip( ";", p->m_messages_in[i] );
			}
		}
		p->m_messages_in.clear();
	}
}

void ScServer::processrace()
{
	if ( m_startRace )
	{
		m_race.reset(new Race(m_map.get(), &logbufferQueue, *Timer::Instance(), m_players, m_dynamicsWorld));
		m_startRace = false;
	}
	
	if ( m_race.get() )
	{
		m_race->process(*Timer::Instance());
	}
}

void ScServer::newLogMessage(const string& msg)
{
	logbufferQueue.push_back(msg);
}

void ScServer::newMessageAllClients(const string& msg)
{
	for ( unsigned int i=0; i < m_players.size(); i++ )
	{
		if ( m_players[i]->isApproved() )
		{
			m_players[i]->pushOutgoingmessage(msg);
		}
	}
}

ScServer::~ScServer()
{
	cerr << "exiting server" << endl;
}
